#include <iostream>
#include <fstream>
#include <cassert>
#include <unordered_set>
#include <cstring>

#include "lex.hpp"

using namespace std;

static const char *token_name[26] = {
	"KW_INT",		// int
	"KW_VOID",		// void
	"KW_RETURN",	// return
	"KW_CONST",		// const
	
	"OP_ADD",			// +
	"OP_SUB",			// -
	"OP_MUL",			// *
	"OP_DIV",			// /
	"OP_MOD",			// %
	"OP_ASSIGN",		// =
	"OP_GT",			// >
	"OP_LT",			// <
	"OP_EQ",			// ==
	"OP_LE",			// <=
	"OP_GE",			// >=
	"OP_NE",			// !=
	"OP_AND",			// &&
	"OP_OR",			// ||

	"SE_LBRAC",		// ( left backet
	"SE_RBRAC",		// ) right bracket
	"SE_LCBRAC",	// { left curly bracket
	"SE_RCBRAC",	// } right curly bracket
	"SE_COMMA",		// ,
	"SE_SEMI",		// ;

	"IDN",			// [a-zA-Z][a-zA-Z_0-9]*
	"INT_VAL",		// -*[0-9]+
};

SymTab sym_tab;

Token::Token(int _ty, char *_val) {
	ty = _ty;
	val = _val;
}

void init_sym_tab() {
	sym_tab.push_back(pair<string, int>("int", int(TokenType::KW_INT)));
	sym_tab.push_back(pair<string, int>("void", int(TokenType::KW_VOID)));
	sym_tab.push_back(pair<string, int>("return", int(TokenType::KW_RETURN)));
	sym_tab.push_back(pair<string, int>("const", int(TokenType::KW_CONST)));
}

int get_dist(int a, int b) {
	if (a >= b) {
		return a-b;
	} else {
		return b-a;
	}
}

bool compare_keyword(string &kw, char *str) {
	int len = strlen(str);
	if (kw.length() != len) {
		return false;
	}
	for (int cnt = 0; cnt < kw.length(); ++cnt) {
		if (kw[cnt] != str[cnt] && get_dist(kw[cnt], str[cnt]) != 32) {
			return false;
		}
	}
	return true;
}

bool compare_iden(string &id, char *str) {
	int len = strlen(str);
	if (id.length() != len) {
		return false;
	}
	for (int cnt = 0; cnt < id.length(); ++cnt) {
		if (id[cnt] != str[cnt]) {
			return true;
		}
	}
	return true;
}

void check_token(Token &tk) {
	if (tk.ty != TokenType::IDN) {
		return;
	}
	for (int cnt = 0; cnt < sym_tab.size(); ++cnt) {
		if (sym_tab[cnt].second >= KW_INT && sym_tab[cnt].second <= KW_CONST) {
			if (compare_keyword(sym_tab[cnt].first, tk.val)) {
				tk.ty = sym_tab[cnt].second;
			}
		} else {
			if (compare_iden(sym_tab[cnt].first, tk.val)) {
				return;
			}
		}
	}
	sym_tab.push_back(pair<string, int>(tk.val, tk.ty));
}

void make_tokens(DFA &dfa, char *input, vector<Token> &tokens) {
	cout << "Making tokens..." << endl << endl;
	tokens.clear();
	dfa.openfile(input);
	while (1) {
		Token tk(0, nullptr);
		if (dfa.get_token(tk) != -1) {
			check_token(tk);
			tokens.push_back(Token(tk.ty, tk.val));
		} else {
			if (tk.val != nullptr) {
				check_token(tk);
				tokens.push_back(Token(tk.ty, tk.val));
			}
			break;
		}
	}
	dfa.closefile();
	cout << "Finished" << endl << endl;
}

string std_name(Token &tk) {
	switch(tk.ty) {
		case KW_INT: return string("KW");//+to_string(1);			// int
		case KW_VOID: return string("KW");//+to_string(2);			// void
		case KW_RETURN: return string("KW");//+to_string(3);		// return
		case KW_CONST: return string("KW");//+to_string(4);		// const
		
		case OP_ADD: return string("OP");//+to_string(6);			// +
		case OP_SUB: return string("OP");//+to_string(7);			// -
		case OP_MUL: return string("OP");//+to_string(8);			// *
		case OP_DIV: return string("OP");//+to_string(9);			// /
		case OP_MOD: return string("OP");//+to_string(10);			// %
		case OP_ASSIGN: return string("OP");//+to_string(11);		// =
		case OP_GT: return string("OP");//+to_string(12);			// >
		case OP_LT: return string("OP");//+to_string(13);			// <
		case OP_EQ: return string("OP");//+to_string(14);			// ==
		case OP_LE: return string("OP");//+to_string(15);			// <=
		case OP_GE: return string("OP");//+to_string(16);			// >=
		case OP_NE: return string("OP");//+to_string(17);			// !=
		case OP_AND: return string("OP");//+to_string(18);			// &&
		case OP_OR: return string("OP");//+to_string(19);			// ||

		case SE_LBRAC: return string("SE");//+to_string(20);		// ( left backet
		case SE_RBRAC: return string("SE");//+to_string(21);		// ) right bracket
		case SE_LCBRAC: return string("SE");//+to_string(22);		// { left curly bracket
		case SE_RCBRAC: return string("SE");//+to_string(23);		// } right curly bracket
		case SE_COMMA: return string("SE");//+to_string(25);		// ,
		case SE_SEMI: return string("SE");//+to_string(24);		// ;

		case IDN: return string("IDN");//+tk.val;					// [a-zA-Z][a-zA-Z_0-9]*
		case INT_VAL: return string("INT");//+tk.val;				// -*[0-9]+
	}
	return string("");
}

void print_tokens(ostream &out, vector<Token> &tokens) {
	for (int cnt = 0; cnt < tokens.size(); ++cnt) {
		out << tokens[cnt].val << "\t<" << std_name(tokens[cnt]) << '>'<< endl;
	}
}

std::string Token::grammar_name() {
	switch(ty) {
		case KW_INT: return "int";		// int
		case KW_VOID: return "void";		// void
		case KW_RETURN: return "return";		// return
		case KW_CONST: return "const";		// const
		
		case OP_ADD: return "+";		// +
		case OP_SUB: return "-";		// -
		case OP_MUL: return "*";		// *
		case OP_DIV: return "/";		// /
		case OP_MOD: return "%";		// %
		case OP_ASSIGN: return "=";		// =
		case OP_GT: return ">";			// >
		case OP_LT: return "<";			// <
		case OP_EQ: return "==";		// ==
		case OP_LE: return "<=";		// <=
		case OP_GE: return ">=";		// >=
		case OP_NE: return "!=";		// !=
		case OP_AND: return "&&";		// &&
		case OP_OR: return "||";		// ||

		case SE_LBRAC: return "(";	// ( left backet
		case SE_RBRAC: return ")";	// ) right bracket
		case SE_LCBRAC: return "{";	// { left curly bracket
		case SE_RCBRAC: return "}";	// } right curly bracket
		case SE_COMMA: return ",";	// ,
		case SE_SEMI: return ";";	// ;

		case IDN: return "IDN";		// [a-zA-Z][a-zA-Z_0-9]*
		case INT_VAL: return "INT";	// -*[0-9]+
		default: cerr << "Token type error: " << ty << endl; exit(-1);
	}
}